 /*
  * Copyright (C) yajin 2008 <yajinzhou@gmail.com >
  *     
  * This file is part of the virtualmips distribution. 
  * See LICENSE file for terms of the license. 
  *
  */

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <pthread.h>

#include "cpu.h"
#include "mips64_memory.h"
#include "device.h"
#include "mips64.h"
#include "mips64_cp0.h"
#include "mips64_exec.h"
#include "vm.h"



#define LOG_ENABLE 0
/* Log a message for a CPU */
void cpu_log(cpu_mips_t * cpu, char *module, char *format, ...)
{
#if LOG_ENABLE
   char buffer[256];
   va_list ap;

   va_start(ap, format);
   snprintf(buffer, sizeof(buffer), "CPU%u: %s", cpu->id, module);
   vm_flog(cpu->vm, buffer, format, ap);
   va_end(ap);
#endif
}

void cpu_log1(cpu_mips_t * cpu, char *module, char *format, ...)
{
   char buffer[256];
   va_list ap;
   va_start(ap, format);
   snprintf(buffer, sizeof(buffer), "CPU%u: %s", cpu->id, module);
   vm_flog(cpu->vm, buffer, format, ap);
   va_end(ap);
}



/* Start a CPU */
void cpu_start(cpu_mips_t * cpu)
{
   if (cpu)
   {
      cpu->state = CPU_STATE_RUNNING;
   }
}

/* Stop a CPU */
void cpu_stop(cpu_mips_t * cpu)
{
   if (cpu)
   {
      cpu_log(cpu, "CPU_STATE", "Halting CPU (old state=%u)...\n", cpu->state);
      cpu->state = CPU_STATE_HALTED;
   }
}
void cpu_restart(cpu_mips_t * cpu)
{
   if (cpu)
   {
      cpu_log(cpu, "CPU_STATE", "Restartting CPU (old state=%u)...\n", cpu->state);
      cpu->state = CPU_STATE_RESTARTING;
   }
}

/* Create a new CPU */
cpu_mips_t *cpu_create(vm_instance_t * vm, u_int type, u_int id)
{
   cpu_mips_t *cpu;

   if (!(cpu = malloc(sizeof(*cpu))))
      return NULL;

   memset(cpu, 0, sizeof(*cpu));
   cpu->vm = vm;
   cpu->id = id;
   cpu->type = type;
   cpu->state = CPU_STATE_SUSPENDED;
   cpu->vm = vm;
   mips64_init(cpu);
   return cpu;


}


/* Delete a CPU */
void cpu_delete(cpu_mips_t * cpu)
{
   if (cpu)
   {
      /* Stop activity of this CPU */
      cpu_stop(cpu);
      pthread_join(cpu->cpu_thread, NULL);
      mips64_delete(cpu);
      free(cpu);
   }
}





/* Find a CPU in a group given its ID */
cpu_mips_t *cpu_group_find_id(cpu_group_t * group, u_int id)
{
   cpu_mips_t *cpu;

   if (!group)
      return NULL;

   for (cpu = group->cpu_list; cpu; cpu = cpu->next)
      if (cpu->id == id)
         return cpu;

   return NULL;
}

/* Find the highest CPU ID in a CPU group */
int cpu_group_find_highest_id(cpu_group_t * group, u_int * highest_id)
{
   cpu_mips_t *cpu;
   u_int max_id = 0;

   if (!group || group->cpu_list)
      return (-1);

   for (cpu = group->cpu_list; cpu; cpu = cpu->next)
      if (cpu->id >= max_id)
         max_id = cpu->id;

   *highest_id = max_id;
   return (0);
}

/* Add a CPU in a CPU group */
int cpu_group_add(cpu_group_t * group, cpu_mips_t * cpu)
{
   if (!group)
      return (-1);

   /* check that we don't already have a CPU with this id */
   if (cpu_group_find_id(group, cpu->id) != NULL)
   {
      fprintf(stderr, "cpu_group_add: CPU%u already present in group.\n", cpu->id);
      return (-1);
   }

   cpu->next = group->cpu_list;
   group->cpu_list = cpu;
   return (0);
}

/* Create a new CPU group */
cpu_group_t *cpu_group_create(char *name)
{
   cpu_group_t *group;

   if (!(group = malloc(sizeof(*group))))
      return NULL;

   group->name = name;
   group->cpu_list = NULL;
   return group;
}

/* Delete a CPU group */
void cpu_group_delete(cpu_group_t * group)
{
   cpu_mips_t *cpu, *next;

   if (group != NULL)
   {
      for (cpu = group->cpu_list; cpu; cpu = next)
      {
         next = cpu->next;
         cpu_delete(cpu);
      }

      free(group);
   }
}

/* Rebuild the MTS subsystem for a CPU group */
int cpu_group_rebuild_mts(cpu_group_t * group)
{
   cpu_mips_t *cpu;

   for (cpu = group->cpu_list; cpu; cpu = cpu->next)
      cpu->mts_rebuild(cpu);

   return (0);
}


/* Start all CPUs of a CPU group */
void cpu_group_start_all_cpu(cpu_group_t * group)
{
   cpu_mips_t *cpu;

   for (cpu = group->cpu_list; cpu; cpu = cpu->next)
      cpu_start(cpu);
}

/* Stop all CPUs of a CPU group */
void cpu_group_stop_all_cpu(cpu_group_t * group)
{
   cpu_mips_t *cpu;

   for (cpu = group->cpu_list; cpu; cpu = cpu->next)
      cpu_stop(cpu);
}

/* Set a state of all CPUs of a CPU group */
void cpu_group_set_state(cpu_group_t * group, u_int state)
{
   cpu_mips_t *cpu;

   for (cpu = group->cpu_list; cpu; cpu = cpu->next)
      cpu->state = state;
}

/* Returns TRUE if all CPUs in a CPU group are inactive */
static int cpu_group_check_activity(cpu_group_t * group)
{
   cpu_mips_t *cpu;

   for (cpu = group->cpu_list; cpu; cpu = cpu->next)
   {
      if (!cpu->cpu_thread_running)
         continue;
      if ((cpu->state == CPU_STATE_RUNNING))
         return (FALSE);
   }

   return (TRUE);
}

/* Synchronize on CPUs (all CPUs must be inactive) */
int cpu_group_sync_state(cpu_group_t * group)
{
   cpu_mips_t *cpu;
   m_tmcnt_t t1, t2;

   /* Check that CPU activity is really suspended */
   t1 = m_gettime();

   for (cpu = group->cpu_list; cpu; cpu = cpu->next)
      cpu->seq_state = 0;

   while (!cpu_group_check_activity(group))
   {
      t2 = m_gettime();

      if (t2 > (t1 + 10000))
         return (-1);

      usleep(50000);
   }

   return (0);
}

/* Save state of all CPUs */
int cpu_group_save_state(cpu_group_t * group)
{
   cpu_mips_t *cpu;

   for (cpu = group->cpu_list; cpu; cpu = cpu->next)
      cpu->prev_state = cpu->state;

   return (TRUE);
}

/* Restore state of all CPUs */
int cpu_group_restore_state(cpu_group_t * group)
{
   cpu_mips_t *cpu;

   for (cpu = group->cpu_list; cpu; cpu = cpu->next)
      cpu->state = cpu->prev_state;

   return (TRUE);
}
